package com.fancy.cc.security.aspect;

import com.alibaba.fastjson.JSONObject;
import com.fancy.cc.model.dto.UmsUserDTO;
import com.fancy.cc.security.domain.AdminUserDetails;
import com.fancy.cc.security.util.CurrentUserUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author: yongxin@mail.thefancyapp.com
 * @program: fancy
 * @description: 出入参统一处理
 * @create: 2020-05-19 12:16
 * <p>
 * RequestContextHolder uses a ThreadLocal to find the context object.
 * Holder class to expose the web request in the form of a thread-bound RequestAttributes object.
 * Using parallel streams means that your code is running in a different thread, so no context object.
 **/
@Aspect
@Component
@ConditionalOnClass(HttpServletRequest.class)
@Order(1)
@Slf4j
public class RequestLogAspect {

    //com.fancy.cc.controller.cms
    @Pointcut("execution(public * com.fancy.*.controller.*.*.*(..))")
    public void requestLog() {
    }

    @Value("${spring.profiles.active}")
    private String env = "test";

    private ThreadLocal<Long> time = new ThreadLocal<>();

    private ThreadLocal<String> threadLocal = new ThreadLocal<>();

    private ThreadLocal<Object> threadLocalParam = new ThreadLocal<>();

    private static SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


    @Before("requestLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    /*if ("local".equals(env)) {
      return;
    }*/
        // 接收到请求，记录请求内容
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        if (null != attributes) {
            HttpServletRequest request = attributes.getRequest();

//      if ("".equals(request.getContentType())) {
//
//      }
            //获取请求参数
            Object params = getMethodParams(joinPoint, request);
            threadLocalParam.set(params);

            String requestID = UUID.randomUUID().toString();
            threadLocal.set(requestID);

            time.set(System.currentTimeMillis());
            LoggerHandler.info(LoggerHandler.TYPE.REQUEST_START,
                    request.getServerName(),
                    IpUtil.getIpAddr(request),
                    request.getRequestURI(),
                    printHead(request),
                    params,
                    request.getMethod(),
                    requestID
            );
            //推送钉钉消息内容
      /*String format = LoggerHandler.format(LoggerHandler.TYPE.REQUEST_START,
              request.getServerName(),
              IpUtil.getIpAddr(request),
              request.getRequestURI(),
              printHead(request),
              params,
              request.getMethod(),
              requestID
      );
      String content = "【test】"+format;
      if ("prod".equals(env)) {
        content = "【prod】"+format;
      } else if("pre".equals(env)){
        content = "【pre】"+format;
      }
      DingTalkUtils.sendToDingdingText(content);*/
        }
    }

    @AfterReturning(returning = "result", pointcut = "requestLog()")
    public void doAfterReturning(Object result) throws Throwable {
        // 处理完请求，记录响应信息
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        if (null != attributes) {
            HttpServletRequest request = attributes.getRequest();

            Long start = time.get();
            long costTime = -1;
            if (start != null) {
                costTime = System.currentTimeMillis() - start;
                time.remove();
            }
            //过滤掉首页的时间线接口
      /*if ("/home/getHomeCustomTem".equalsIgnoreCase(request.getRequestURI())
          || "/live-room/list".equalsIgnoreCase(request.getRequestURI())) {
        result = "..成功...";
      }*/
            String requestID = threadLocal.get();
            Object params = threadLocalParam.get();
            LoggerHandler.info(LoggerHandler.TYPE.REQUEST_END,
                    request.getServerName(),
                    IpUtil.getIpAddr(request),
                    request.getRequestURI(),
                    params,
                    request.getMethod(),
                    result,
                    costTime,
                    requestID
            );
            if (!Objects.isNull(params)) {
                threadLocalParam.remove();
            }
            if (StringUtils.isNotBlank(requestID)) {
                threadLocal.remove();
            }
        }
    }

    @AfterThrowing(pointcut = "requestLog()", throwing = "ex")
    public void doAfterThrowing(Throwable ex) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes();
        if (null != attributes) {
            HttpServletRequest request = attributes.getRequest();
            Long start = time.get();
            Long costTime = 0L;
            if (start != null) {
                costTime = System.currentTimeMillis() - start;
            }
            String requestID = threadLocal.get();
            Object params = threadLocalParam.get();
            LoggerHandler.info(LoggerHandler.TYPE.REQUEST_ERROR,
                    request.getServerName(),
                    IpUtil.getIpAddr(request),
                    request.getRequestURI(),
                    printHead(request),
                    params,
                    request.getMethod(),
                    ex.getMessage(),
                    costTime,
                    requestID
            );
            if (Objects.nonNull(start)) {
                time.remove();
            }
            if (StringUtils.isNotBlank(requestID)) {
                threadLocal.remove();
            }
            if (!Objects.isNull(params)) {
                threadLocalParam.remove();
            }
        }
    }

    /**
     * 获取请求参数
     *
     * @param joinPoint
     * @param request
     * @return
     */
    private Object getMethodParams(JoinPoint joinPoint, HttpServletRequest request) {
        Object params = null;
        if ("POST".equalsIgnoreCase(request.getMethod())) {
            if (Objects.nonNull(joinPoint.getArgs()) && (joinPoint.getArgs().length > 0)) {
                //序列化时过滤掉request和response 和框架定义的WxRequestContext
                List<Object> logArgs = streamOf(joinPoint.getArgs())
                        .filter(arg -> (!(arg instanceof HttpServletRequest)
                                && !(arg instanceof HttpServletResponse)
                                && !(arg instanceof MultipartFile)
                        ))
                        .collect(Collectors.toList());
                params = logArgs;
            }
        } else if ("GET".equalsIgnoreCase(request.getMethod())) {
            params = request.getParameterMap()
                    .entrySet()
                    .stream()
                    .map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue()))
                    .collect(Collectors.joining("&"));
        }

        return params;
    }

    public static <T> Stream<T> streamOf(T[] array) {
        return ArrayUtils.isEmpty(array) ? Stream.empty() : Arrays.asList(array).stream();
    }

    /**
     * 打印头信息--只获取fancy 的token信息
     *
     * @param request
     * @return
     */
    private static String printHead(HttpServletRequest request) {
        AdminUserDetails adminUserDetails = CurrentUserUtil.getCurrentUser();
        if (Objects.isNull(adminUserDetails)) {
            return "";
        }
        JSONObject jsonObject = new JSONObject();
        Date loginDate = new Date();
        jsonObject.put("username", adminUserDetails.getUsername());
        jsonObject.put("merchantId", adminUserDetails.getMerchantId());
        UmsUserDTO umsUser = adminUserDetails.getUmsUserDTO();
        if (!Objects.isNull(umsUser)) {
            jsonObject.put("nickName", umsUser.getNickName());
            if (!Objects.isNull(umsUser.getLoginTime())) {
                loginDate = umsUser.getLoginTime();
            }
            jsonObject.put("loginTime", formatter.format(loginDate));
        }
        return jsonObject.toJSONString();
    }
}
